﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.IO.Compression;
using System.Diagnostics;
using ICSharpCode.SharpZipLib.Zip;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
using AbstraX.Contracts;
using System.Configuration;
using AbstraX;

namespace VisualStudioProvider.Templates
{
    public static class TemplateProvider
    {
        private static ManualResetEvent indexerEvent;
        private static Thread indexerThread;
        private static bool indexStarted;
        private static Dictionary<string, ICodeTemplate> itemTemplates;
        private static Dictionary<string, ICodeTemplate> projectTemplates;
        private static Dictionary<string, ICodeTemplate> projectGroupTemplates;
        private const int INDEX_WAIT_MINUTES = 10;

        static TemplateProvider()
        {
            if (!indexStarted)
            {
                Index();
            }
        }

        public static void Index()
        {
            if (!indexStarted)
            {
                itemTemplates = new Dictionary<string, ICodeTemplate>();
                projectTemplates = new Dictionary<string, ICodeTemplate>();
                projectGroupTemplates = new Dictionary<string, ICodeTemplate>();

                indexerEvent = new ManualResetEvent(false);
                indexerThread = new Thread(IndexerThread);

                indexerThread.Priority = ThreadPriority.Lowest;

                indexerThread.Start();

                indexStarted = true;
            }
        }

        private static void IndexerThread()
        {
            var templateDirectory = new DirectoryInfo(VSConfig.ProjectTemplateDirectory);
            var additionalLocations = (List<CustomTemplateLocation>)ConfigurationManager.GetSection("VSTemplateLocationsSection/VSTemplateLocations");

            Debug.Assert(templateDirectory.Exists);

            if (TempWorkspace.Exists)
            {
                RecurseDelete(TempWorkspace);
            }

            RecurseDirectory(templateDirectory);

            additionalLocations.ForEach(c => RecurseDirectory(new DirectoryInfo(c.TemplateLocation), new DirectoryInfo(c.CommonLocation)));

            if (TempWorkspace.Exists)
            {
                RecurseDelete(TempWorkspace);
            }

            indexerEvent.Set();
        }

        private static DirectoryInfo TempWorkspace
        {
            get
            {
                return new DirectoryInfo(Path.Combine(Path.GetTempPath(), @"SolutionFramework\TemplateProviderWorkspace"));
            }
        }

        private static void RecurseDelete(string directoryPath)
        {
            var directory = new DirectoryInfo(directoryPath);

            RecurseDelete(directory);
        }

        private static void RecurseDelete(DirectoryInfo directory)
        {
            foreach (var fileInfo in directory.GetFiles())
            {
                fileInfo.Delete();
            }

            foreach (var subDir in directory.GetDirectories())
            {
                RecurseDelete(subDir);
            }

            directory.Delete(true);
        }

        private static void RecurseDirectory(DirectoryInfo directory, DirectoryInfo commonLocation = null)
        {
            foreach (var fileInfo in directory.GetFiles("*.zip"))
            {
                ProcessZip(fileInfo, commonLocation);
            }

            foreach (var subDir in directory.GetDirectories())
            {
                RecurseDirectory(subDir, commonLocation);
            }
        }

        private static string GetPathPrefix(FileInfo file)
        {
            if (file.DirectoryName.ToLower().StartsWith(VSConfig.ItemTemplateDirectory.ToLower()))
            {
                return file.DirectoryName.Substring(VSConfig.ItemTemplateDirectory.Length);
            }
            else if (file.DirectoryName.ToLower().StartsWith(VSConfig.ProjectTemplateDirectory.ToLower()))
            {
                return file.DirectoryName.Substring(VSConfig.ProjectTemplateDirectory.Length);
            }
            else
            {
                return file.Directory.Name;
            }
        }

        private static string GetTemplateTypeFromPath(FileInfo file)
        {
            if (file.DirectoryName.ToLower().StartsWith(VSConfig.ItemTemplateDirectory.ToLower()))
            {
                return Enum.GetName(typeof(TemplateType), TemplateType.Item);
            }
            else if (file.DirectoryName.ToLower().StartsWith(VSConfig.ProjectTemplateDirectory.ToLower()))
            {
                return Enum.GetName(typeof(TemplateType), TemplateType.Project);
            }
            else
            {
                return Enum.GetName(typeof(TemplateType), TemplateType.Custom);
            }
        }

        public static Dictionary<string, ICodeTemplate> ProjectGroupTemplates
        {
            get
            {
                if (!indexerEvent.WaitOne(new TimeSpan(0, INDEX_WAIT_MINUTES, 0)))
                {
                    throw new Exception("");
                }

                return projectGroupTemplates;
            }
        }

        public static Dictionary<string, ICodeTemplate> ProjectTemplates
        {
            get
            {
                if (!indexerEvent.WaitOne(new TimeSpan(0, INDEX_WAIT_MINUTES, 0)))
                {
                    throw new Exception("");
                }

                return projectTemplates;
            }
        }

        public static Dictionary<string, ICodeTemplate> ItemTemplates
        {
            get 
            {
                if (!indexerEvent.WaitOne(new TimeSpan(0, INDEX_WAIT_MINUTES, 0)))
                {
                    throw new Exception("");
                }

                return itemTemplates;
            }
        }

        public static string Decompress(FileInfo fileInfo, string outputDirectory, List<string> skip = null)
        {
            var templateFilePath = (string)null;

            using (var stream = fileInfo.OpenRead())
            {
                var curFile = fileInfo.FullName;
                var origName = curFile.Remove(curFile.Length - fileInfo.Extension.Length);

                using (var zipStream = new ZipInputStream(stream))
                {
                    ZipEntry entry;

                    while ((entry = zipStream.GetNextEntry()) != null)
                    {
                        if (entry.IsFile)
                        {
                            if (skip == null || !skip.Any(s => s.ToLower() == entry.Name.ToLower()))
                            {
                                var filePath = Path.Combine(outputDirectory, entry.Name);
                                var fileInfo2 = new FileInfo(filePath);
                                var directory = new DirectoryInfo(fileInfo2.DirectoryName);

                                if (entry.Name.ToLower().EndsWith(".vstemplate"))
                                {
                                    templateFilePath = filePath;
                                }

                                if (!directory.Exists)
                                {
                                    directory.Create();
                                }

                                using (var streamWriter = File.Create(filePath))
                                {
                                    var size = 2048;
                                    var data = new byte[2048];

                                    while (true)
                                    {
                                        size = zipStream.Read(data, 0, data.Length);

                                        if (size > 0)
                                        {
                                            streamWriter.Write(data, 0, size);
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }

                                    streamWriter.Flush();
                                    streamWriter.Close();
                                }
                            }
                        }
                    }
                }
            }

            Debug.Assert(!string.IsNullOrEmpty(templateFilePath));

            return templateFilePath;
        }

        private static void ProcessZip(FileInfo fileInfo, DirectoryInfo commonLocation = null)
        {
            var templateType = GetTemplateTypeFromPath(fileInfo);
            var pathPrefix = GetPathPrefix(fileInfo);
            var outputDirectory = Path.Combine(Path.Combine(TempWorkspace.FullName, templateType + "Templates"), pathPrefix);
            var templateFilePath = Decompress(fileInfo, outputDirectory);
            var document = XDocument.Load(templateFilePath);
            var r = new XmlNamespaceManager(new NameTable());
            var namespaceURL = "http://schemas.microsoft.com/developer/vstemplate/2005";

            r.AddNamespace("p", namespaceURL);

            var typeName = document.XPathSelectElement("/p:VSTemplate", r).Attribute("Type").Value;
            var name = document.XPathSelectElement("/p:VSTemplate/p:TemplateData/p:Name", r).Value;
            var description = document.XPathSelectElement("/p:VSTemplate/p:TemplateData/p:Description", r).Value;
            var defaultName = document.XPathSelectElement("/p:VSTemplate/p:TemplateData/p:DefaultName", r).Value;
            var projectTypeName = document.XPathSelectElement("/p:VSTemplate/p:TemplateData/p:ProjectType", r).Value;
            var key = @"\" + Path.Combine(pathPrefix, Path.GetFileNameWithoutExtension(fileInfo.Name));

            if (string.IsNullOrEmpty(name))
            {
                name = defaultName;
            }

            var vsTemplate = new VSTemplate
            {
                Name = name,
                TypeName = typeName,
                Description = description,
                DefaultName = defaultName,
                ProjectTypeName = projectTypeName,
                ZippedTemplate = fileInfo,
                TemplateDocument = document,
                CommonLocation = commonLocation
            };

            var projects = document.XPathSelectElements("/p:VSTemplate/p:TemplateContent/p:Project", r);
            var _namespace = (XNamespace)namespaceURL;

            foreach (var project in projects)
            {
                var templateProject = new VSTemplateProject
                {
                    File = project.Attribute("File").Value,
                    ReplaceParameters = bool.Parse(project.Attribute("ReplaceParameters").Value)
                };

                vsTemplate.Projects.Add(templateProject);

                foreach (var projectItem in project.Descendants(_namespace + "ProjectItem"))
                {
                    var folder = string.Empty;
                    var parent = projectItem.Parent;

                    // YOU ARE HERE!!! - handle moving file to right place

                    while (parent != project)
                    {
                        folder = @"\" + parent.Attribute("Name").Value + folder;
                        parent = parent.Parent;
                    }

                    if (folder.StartsWith(@"\"))
                    {
                        folder = folder.Remove(0, 1);
                    }

                    templateProject.ProjectItems.Add(new VSTemplateProjectItem
                    {
                        FileName = projectItem.Value,
                        ReplaceParameters = projectItem.Attributes().Any(a => a.Name == "ReplaceParameters") ? bool.Parse(projectItem.Attribute("ReplaceParameters").Value) : false,
                        TargetFileName = projectItem.Attributes().Any(a => a.Name == "TargetFileName") ? projectItem.Attribute("TargetFileName").Value : null,
                        Folder = folder
                    });
                }
            }

            Console.WriteLine("Processed " + vsTemplate.Name);

            if (vsTemplate.TemplateType == TemplateType.Item)
            {
                itemTemplates.Add(key, vsTemplate);
            }
            else if (vsTemplate.TemplateType == TemplateType.Project)
            {
                projectTemplates.Add(key, vsTemplate);
            }
            else if (vsTemplate.TemplateType == TemplateType.ProjectGroup)
            {
                projectGroupTemplates.Add(key, vsTemplate);
            }
            else
            {
                Debugger.Break();
            }
        }
    }
}
